/**
 * @author :    hippo
 * @date :      2023-08-02
 * @copyright   Apache 2.0
*/

#ifndef _BLOCKQUEUE_H
#define _BLOCKQUEUE_H

#include <mutex>
#include <deque>
#include <condition_variable>
#include <sys/time.h>
#include <assert.h>

template<typename T>
class BlockDeque {
private:
    std::deque<T> deq_;
    size_t capacity_;
    std::mutex mutex_;
    bool is_close_;
    std::condition_variable cv_consumer_;
    std::condition_variable cv_producer_;

public:
    explicit BlockDeque(size_t max_capacity = 1000);
    ~BlockDeque();

    void clear();
    
    bool empty();
    bool full();
    void close();

    size_t size();
    size_t capacity();
    
    T front();
    T back();
    
    void pushBack(const T &item);
    void pushFront(const T &item);
    bool pop(T &item);
    bool pop(T &item, int timeout);
    void flush();
};

template<typename T>
BlockDeque<T>::BlockDeque(size_t max_capacity): capacity_(max_capacity), is_close_(false) {
    assert(max_capacity > 0);
}

template<typename T>
BlockDeque<T>::~BlockDeque() {
    close();
}

template<typename T>
void BlockDeque<T>::close() {
    {
        std::lock_guard<std::mutex> locker(mutex_);
        deq_.clear();
        is_close_ = true;
    }
    cv_producer_.notify_all();
    cv_producer_.notify_all();
}

template<typename T>
void BlockDeque<T>::flush() {
    cv_consumer_.notify_one();
}

template<typename T>
void BlockDeque<T>::clear() {
    std::lock_guard<std::mutex> locker(mutex_);
    deq_.clear();
}

template<typename T>
T BlockDeque<T>::front() {
    std::lock_guard<std::mutex> locker(mutex_);
    return deq_.front();
}

template<typename T>
T BlockDeque<T>::back() {
    std::lock_guard<std::mutex> locker(mutex_);
    return deq_.back();
}

template<typename T>
size_t BlockDeque<T>::size() {
    std::lock_guard<std::mutex> locker(mutex_);
    return deq_.size();
}

template<typename T>
size_t BlockDeque<T>::capacity() {
    std::lock_guard<std::mutex> locker(mutex_);
    return deq_.capacity();
}

template<typename T>
void BlockDeque<T>::pushBack(const T &item) {
    std::unique_lock<std::mutex> locker(mutex_);
    while(deq_.size() >= capacity_) {
        cv_producer_.wait(locker);
    }
    deq_.push_back(item);
    cv_consumer_.notify_one();
}

template<typename T>
void BlockDeque<T>::pushFront(const T &item) {
    std::unique_lock<std::mutex> locker(mutex_);
    while(deq_.size() >= capacity_) {
        cv_producer_.wait(locker);
    }
    deq_.push_front(item);
    cv_consumer_.notify_one();
}

template<typename T>
bool BlockDeque<T>::empty() {
    std::lock_guard<std::mutex> locker(mutex_);
    return deq_.empty();
}

template<typename T>
bool BlockDeque<T>::full() {
    std::lock_guard<std::mutex> locker(mutex_);
    return deq_.size() >= capacity_;
}

template<typename T>
bool BlockDeque<T>::pop(T &item) {
    std::unique_lock<std::mutex> locker(mutex_);
    while(deq_.empty()) {
        cv_consumer_.wait(locker);
        if(is_close_) {
            return false;
        }
    }
    item = deq_.front();
    deq_.pop_front();
    cv_producer_.notify_one();
    return true;
}

template<typename T>
bool BlockDeque<T>::pop(T &item, int timeout) {
    std::unique_lock<std::mutex> locker(mutex_);
    while(deq_.empty()) {
        if(cv_consumer_.wait_for(locker, std::chrno::seconds(timeout)) == std::cv_status::timeout) {
            return false;
        }
        if(is_close_) {
            return false;
        }
    }
    item = deq_.front();
    deq_.pop_front();
    cv_producer_.notify_one();
    return true;
}

#endif // _BLOCKQUEUE_H